iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0

昨天我們把 [13, 13] 寫死在測試裡,確實看到多領。但「要重複放 13」是我先告訴 Foundry 的。這次不寫死第二個 epoch,讓它自己選。

還有一件事:把 fuzz 設成跑 24 次,是不是真的有 24 組輸入進到測試?

先讓 Foundry 換輸入

沿用 Day 12 的 BSC 區塊 27,830,139 快照,測的還是當時的 LevelReferralControllerV2。先在 epoch 13 賺到獎勵、領一部分,等獎勵繼續解鎖,然後呼叫 claimMultiple。

第一個 epoch 固定是 13,第二個讓 Foundry 在 12 和 13 之間選。這份快照的準備流程只在 13 賺到獎勵,拿一堆沒有獎勵的 epoch 來試,測不到這個問題。

等待時間也交給 Foundry,在 0 到 5 小時之間選。時間不同,解鎖的獎勵就不同;我沒有直接改合約裡存的獎勵數字。另外讓它選領款地址,以及由原本帳戶或另一個同樣賺過獎勵的帳戶呼叫。領款地址會變,但合約查的是呼叫者 msg.sender 的獎勵,兩件事不能混在一起。

第二次領款前先記下可領量,領款後看實際拿到多少。重複的 epoch 只能算一次。測試怎麼把輸入送進合約,摘幾行來看:

vm.warp(block.timestamp + bound(uint256(rawHours), 0, 5) * 1 hours);
uint256 secondEpoch = bound(uint256(rawEpoch), 12, 13);
uint256[] memory epochs = new uint256[](2);
epochs[0] = EPOCH;
epochs[1] = secondEpoch;
vm.prank(caller);
CONTROLLER.claimMultiple(epochs, recipient);

跑出來的反例

我把 fuzz 設成 24 次、seed 設成 0x123456。清掉舊反例的快取後,其中一次跑出來是這樣:

[PASS] testFuzz_Fixed(uint8,address,uint8,bool) (runs: 24)
[FAIL: paid more than distinct epochs allow: 8096335669924762955 > 7286702102932286660]
  testFuzz_Vulnerable(uint8,address,uint8,bool) (runs: 1)
  args=[139, 0x0000000000000000000000000000000000007253, 52, false]
  epoch pair 13 13

被攻擊版在第 1 次就 FAIL。args 裡的 139 不是 epoch 139;測試會用 bound(rawEpoch, 12, 13) 把它換成 13。52 也會換成再等 4 小時,所以最後送進 claimMultiple 的是 [13, 13]。false 表示這次用原本的帳戶。

呼叫前,不重複計算 epoch 13 的話,還能領 7.2867 LVL;合約卻付了 8.0963 LVL。多的約 0.8096 LVL,就是之前已經領過的那部分。修補版在相同輸入範圍下跑滿 24 次,沒有出現這個失敗。

我清掉快取再跑一遍,同一個 seed 這次找到的是 rawEpoch=3、rawHours=91,跑到第 2 次才 FAIL。換算後仍是再等 1 小時、送入 [13, 13]。反例的原始數字不一定一樣,先看最後送進合約的是什麼。

24 次都跑到了嗎?

假設只想測 epoch 12 和 13,可以把產生的數字壓進範圍,也可以把範圍外的數字丟掉:

// 把任何產生的數字映射到 12 或 13
uint256 epoch = bound(rawEpoch, 12, 13);

// 不在 12 到 13 之間就丟掉這組輸入
vm.assume(rawEpoch >= 12 && rawEpoch <= 13);

兩個測試用同一份快照、同一個 seed,最多允許拒絕 24 組輸入。結果:

[PASS] testFuzz_BoundSingleEpoch(uint256) (runs: 24)
[FAIL: `vm.assume` rejected too many inputs (24 allowed)]
  testFuzz_AssumeSingleEpoch(uint256) (runs: 0)

bound 跑完了 24 次;assume 一組有效輸入都沒留下。它不是不能用,像排除零地址這種少數不需要的輸入就很適合。但從整個 uint256 範圍裡等到 12 或 13,幾乎只會一直丟掉輸入。

bound 的 24 次也不是 24 種 epoch,最後仍只有 12、13 兩種。這次有找到多付的反例;修補版跑滿 24 次,不能當成所有輸入都安全的證明。

明日預告

明天把「同一份獎勵不能領兩次」寫成明確的測試條件,並把這次的反例留下來,之後改合約時還能重跑。

參考資料


上一篇
Day 12 | 測試通過,代表沒問題嗎?
下一篇
Day 14|錢領對了 帳卻記錯了
系列文
從 Detect 到 Proof:智慧合約審計工具實戰 共 14 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言